Skip to content

feat: per-file tracking for folder uploads in upload queue#3786

Open
Crash-- wants to merge 10 commits intofix/upload-queue-fileId-identityfrom
feat/folder-upload-per-file-tracking
Open

feat: per-file tracking for folder uploads in upload queue#3786
Crash-- wants to merge 10 commits intofix/upload-queue-fileId-identityfrom
feat/folder-upload-per-file-tracking

Conversation

@Crash--
Copy link
Copy Markdown
Contributor

@Crash-- Crash-- commented Apr 6, 2026

Summary

  • When a folder is dropped, each file now appears individually in the upload queue with its relative path (e.g. photos/2024/img.jpg), individual progress tracking, and per-file error handling
  • Server-side folders are created automatically based on the folder structure
  • The upload queue appears immediately on drop — folder creation happens in the background before uploads start
  • Fix: reducer identity collision when two files share the same name (uses fileId instead of file.name)
  • Fix: reducer fallback on file.name when fileId is absent (Flagship compatibility)
  • Fix: extractFilesEntries calls webkitGetAsEntry() only once per item
  • Fix: createFolder handles 409 conflict (folder already exists)
  • Dual-path support: file.path for react-dropzone, FileSystemEntry for react-dnd (DropzoneDnD)

Known limitations

  • Desktop upload header shows total count but not done/total progression (requires cozy-ui-plus change)
  • No retry per-file (planned for a future PR)

Test plan

  • 42 unit tests passing (including E2E flow tests)
  • 0 lint errors
  • Manual test: drop a folder with sub-folders, verify folders created + files uploaded with relative paths
  • Manual test: drop individual files, verify they upload normally
  • Manual test: drop a folder that already exists on the server (409 handling)
  • Verify Flagship upload path still works (no fileId in dispatch)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved folder upload handling with automatic path preservation during drag-and-drop operations.
    • Implemented server-side folder structure resolution for better folder organization during uploads.
  • Bug Fixes

    • Enhanced file tracking reliability by using unique identifiers instead of file names.
  • Tests

    • Expanded test coverage for folder operations and upload queue management.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e5658524-bc23-49f9-9bb6-62ec3f54d3f0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR refactors the upload module's folder and file handling mechanism. It removes custom folder detection from Dropzone in favor of react-dropzone's File object processing. The core changes introduce fileId-based item identification throughout the upload queue (replacing name-based matching), add server-side folder resolution via new utilities (flattenEntries and flattenEntriesFromPaths), and update progress tracking to use per-item fileId. The addToUploadQueue flow now enqueues preliminary items before resolving folder structures asynchronously via the new RESOLVE_FOLDER_ITEMS action.

Possibly related PRs

Suggested reviewers

  • lethemanh
  • JF-Cozy
  • doubleface
  • zatteo
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing per-file tracking for folder uploads by flattening folders into individual queue items with relative paths and independent progress tracking.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/folder-upload-per-file-tracking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@bundlemon
Copy link
Copy Markdown

bundlemon bot commented Apr 6, 2026

BundleMon

Files updated (2)
Status Path Size Limits
static/js/main.(hash).js
168.47KB (+890B +0.52%) -
public/static/js/public.(hash).js
142.15KB (+867B +0.6%) -
Unchanged files (19)
Status Path Size Limits
static/js/(chunkId).(hash).js
1.07MB -
public/static/js/(chunkId).(hash).js
1.01MB -
static/js/cozy.(hash).js
894.94KB -
public/static/js/cozy.(hash).js
758.89KB -
(hash).js
336.11KB -
public/(hash).js
336.11KB -
services/qualificationMigration.js
283.13KB -
services/dacc.js
262.86KB -
public/static/js/lib-react.(hash).js
43.88KB -
static/js/lib-react.(hash).js
43.88KB -
public/static/css/cozy.(hash).css
33.78KB -
static/css/cozy.(hash).css
33.78KB -
public/static/js/lib-router.(hash).js
21.86KB -
static/js/lib-router.(hash).js
21.86KB -
static/css/main.(hash).css
17.04KB -
public/static/css/public.(hash).css
6.76KB -
manifest.webapp
3.04KB -
index.html
702B -
assets/manifest.json
185B -

Total files change +1.72KB +0.03%

Groups updated (1)
Status Path Size Limits
**/*.js
7.37MB (+1.72KB +0.02%) -
Unchanged groups (2)
Status Path Size Limits
**/*.{png,svg,ico}
2.19MB -
**/*.css
128.38KB -

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@Crash-- Crash-- changed the base branch from master to fix/upload-queue-fileId-identity April 6, 2026 09:31
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/modules/upload/Dropzone.jsx (1)

51-53: Minor redundancy: filesToUpload alias is unnecessary.

Since the conditional folder-handling logic was removed, filesToUpload is now just a direct alias for files. Consider using files directly in the uploadFiles call.

♻️ Suggested simplification
-    // react-dropzone sets file.path on each File with the relative path.
-    // addToUploadQueue uses file.path to detect and handle folder uploads.
-    const filesToUpload = files
     dispatch(
       uploadFiles(
-        filesToUpload,
+        files, // react-dropzone sets file.path for folder uploads
         displayedFolder.id,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/upload/Dropzone.jsx` around lines 51 - 53, Remove the unnecessary
alias by deleting the declaration const filesToUpload = files and pass files
directly to uploadFiles (and any other call sites that currently use
filesToUpload) in Dropzone.jsx; ensure there are no remaining references to
filesToUpload (e.g., within addToUploadQueue or uploadFiles invocation) so the
code uses the original files variable directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/upload/index.js`:
- Around line 157-163: The RESOLVE_FOLDER_ITEMS reducer currently only updates
existing queue items (using state.map) so files discovered by flattenEntries
(action.resolvedItems) for folder drops are never added; change the
RESOLVE_FOLDER_ITEMS case to merge resolved items into the state by: for each
item in action.resolvedItems, update matching state entries (by fileId) and
collect any resolved items whose fileId is not present in state and append them
(or alternatively dispatch ADD_TO_UPLOAD_QUEUE with those new items); reference
the reducer case handling RESOLVE_FOLDER_ITEMS and the payload field
action.resolvedItems, and ensure this complements where preliminaryItems and
flattenEntries produce folder-derived items.

---

Nitpick comments:
In `@src/modules/upload/Dropzone.jsx`:
- Around line 51-53: Remove the unnecessary alias by deleting the declaration
const filesToUpload = files and pass files directly to uploadFiles (and any
other call sites that currently use filesToUpload) in Dropzone.jsx; ensure there
are no remaining references to filesToUpload (e.g., within addToUploadQueue or
uploadFiles invocation) so the code uses the original files variable directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 417b7482-d5ab-41f5-a3e1-5bbc0b39a228

📥 Commits

Reviewing files that changed from the base of the PR and between 548f0bc and a400368.

📒 Files selected for processing (5)
  • src/modules/upload/Dropzone.jsx
  • src/modules/upload/DropzoneDnD.jsx
  • src/modules/upload/UploadQueue.jsx
  • src/modules/upload/index.js
  • src/modules/upload/index.spec.js

Comment thread src/modules/upload/index.js Outdated
@Crash-- Crash-- force-pushed the feat/folder-upload-per-file-tracking branch from a400368 to a988eef Compare April 6, 2026 09:36
codescene-delta-analysis[bot]

This comment was marked as outdated.

@Crash-- Crash-- force-pushed the feat/folder-upload-per-file-tracking branch from a988eef to bbfe791 Compare April 6, 2026 09:49
codescene-delta-analysis[bot]

This comment was marked as outdated.

@Crash-- Crash-- force-pushed the feat/folder-upload-per-file-tracking branch from bbfe791 to 702fcf9 Compare April 6, 2026 09:52
codescene-delta-analysis[bot]

This comment was marked as outdated.

@Crash--
Copy link
Copy Markdown
Contributor Author

Crash-- commented Apr 6, 2026

Enregistrement.de.l.ecran.2026-04-06.a.17.09.45.mov

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves folder uploads by representing each file as its own upload-queue item (using relative paths as identifiers), enabling per-file progress/error handling, and creating the corresponding server-side folder structure before uploads start.

Changes:

  • Add folder-resolution flow (flattenEntries* + RESOLVE_FOLDER_ITEMS) to expand folder drops into per-file queue items with relativePath and folderId.
  • Update upload processing to upload each item into its resolved target directory (folderId) and to key reducer updates by fileId.
  • Adjust Dropzone behavior to rely on file.path (react-dropzone) and add tests covering the new flattening logic and reducer merge behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/modules/upload/UploadQueue.jsx Displays relative paths in the UI by deriving a display-friendly file.name from relativePath.
src/modules/upload/index.js Introduces folder resolution + queue merge (RESOLVE_FOLDER_ITEMS), updates upload execution to target per-item folderId, and adds 409 handling in createFolder.
src/modules/upload/index.spec.js Adds unit tests for queue merging and both flattening strategies (FileSystemEntry-based and file.path-based).
src/modules/upload/DropzoneDnD.jsx Documents the synchronous nature of react-dnd drops (so webkitGetAsEntry() remains valid).
src/modules/upload/Dropzone.jsx Simplifies drop handling to rely on file.path for folder detection/handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/modules/upload/index.js
Comment thread src/modules/upload/index.js Outdated
Comment thread src/modules/upload/index.js
Comment on lines +43 to +57
const mapStateToProps = state => {
const rawQueue = getUploadQueue(state)

// Replace file.name with relativePath for display when available
const queue = rawQueue.map(item => {
if (!item.relativePath) return item
return {
...item,
file: {
name: item.relativePath,
type: item.file?.type,
size: item.file?.size
}
}
})
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapStateToProps always creates a new queue array via rawQueue.map(...) even when no items have relativePath. This breaks referential equality and can trigger re-renders of the connected UploadQueue on any store update. Consider memoizing this transformation (e.g., reselect) or only mapping when at least one item needs a display override.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@Crash-- Crash-- force-pushed the fix/upload-queue-fileId-identity branch from 20a8482 to fa56779 Compare April 7, 2026 03:40
Crash-- and others added 3 commits April 7, 2026 11:48
…dead code

- Reducer falls back to matching on file.name when fileId is absent,
  ensuring Flagship upload path (which dispatches without fileId) works.
- extractFilesEntries now calls webkitGetAsEntry() only once per item.
- Remove dead code: removeFileToUploadQueue (unused export).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a folder is dropped, each file now appears as its own item in the
upload queue with its relative path (e.g. "photos/2024/img.jpg"),
individual progress tracking, and per-file error handling.

- Add flattenEntriesFromPaths: uses file.path from react-dropzone to
  detect folder structure, create server-side folders, and produce flat
  queue items with relativePath and folderId
- Add flattenEntries: FileSystemEntry-based variant for DropzoneDnD
  where webkitGetAsEntry() is still valid (synchronous drop handler)
- Refactor processNextFile: remove isDirectory branch, use item.folderId
- Fix createFolder: handle 409 conflict (folder already exists)
- Simplify Dropzone.jsx: always pass files (react-dropzone sets file.path)
- UploadQueue.jsx: display relativePath instead of file.name in the queue
- Remove dead code: uploadDirectory, canHandleFolders in Dropzone.jsx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The upload queue now appears instantly when files are dropped. Server-
side folder creation happens in the background, then queue items are
updated with resolved folderId/relativePath via RESOLVE_FOLDER_ITEMS.
Previously the queue only appeared after all folders were created,
causing a noticeable delay on slow connections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Crash-- and others added 6 commits April 7, 2026 11:50
Extract helpers to reduce cyclomatic complexity flagged by CodeScene:
- buildPreliminaryItems: builds initial queue items for immediate display
- resolveServerFolders: dispatches to the right flatten strategy
- cleanFilePath / makeFlatItem / makeFolderItem: reduce branching in
  flattenEntriesFromPaths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When dropping a folder via DropzoneDnD, directory entries have file=null
so buildPreliminaryItems filters them out. flattenEntries then discovers
the actual files inside. The RESOLVE_FOLDER_ITEMS reducer now appends
items whose fileId doesn't exist in the queue yet, instead of only
updating existing ones.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Remove unused performUpload helper after rebase. Fix Prettier
formatting on ternary expressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Crash-- Crash-- force-pushed the feat/folder-upload-per-file-tracking branch from b37e938 to f2132dd Compare April 7, 2026 05:35
codescene-delta-analysis[bot]

This comment was marked as outdated.

Extract updateQueueItem, uploadSingleFile, hasFolderEntries,
notifyFolderError, and ensureCallback to reduce cyclomatic complexity
and method size. Code Health: 7.7 → 9.01.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Health Improved (1 files improve in Code Health)

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

View Improvements
File Code Health Impact Categories Improved
index.js 8.79 → 9.01 Complex Method, Excess Number of Function Arguments

Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.


const notifyFolderError = () => {
if (typeof window !== 'undefined' && typeof window.alert === 'function') {
window.alert('The folder upload could not be prepared. Please try again.')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use showAlert like everywhere else

) => {
const targetDirId = folderId ?? dirID
const encryptionKey = flag('drive.enable-encryption')
? await getEncryptionKeyFromDirId(client, targetDirId)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might need explicit validation since subfolders get their own key lookup. If keys don't inherit to newly created subfolders, encrypted uploads break silently.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in this case, it means that we already have the issue, right? I didn't change the current behavior.

return {
...item,
file: {
name: item.relativePath,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might break icon resolution since we pass a path instead of a file name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm interesting. I'll check that and add a test for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants